//
// mcast_receiver.h
//

#ifndef KITTY_MCAST_RECEIVER_H
#define KITTY_MCAST_RECEIVER_H

#include <string>
#include <sstream>
#include <iomanip>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/archive/text_iarchive.hpp>
#include "network.h"

namespace kitty {

class mcast_receiver
{
public:
    mcast_receiver(boost::asio::io_service& io_service, 
            const boost::asio::ip::address& listen_address,
            const boost::asio::ip::address& multicast_address,
            short multicast_port) : socket_(io_service)
    {
        // Create the socket so that multiple may be bound to the same address.
        boost::asio::ip::udp::endpoint listen_endpoint(
            listen_address, multicast_port);
        socket_.open(listen_endpoint.protocol());
        socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
        socket_.bind(listen_endpoint);

        // Join the multicast group.
        socket_.set_option(
            boost::asio::ip::multicast::join_group(multicast_address));
    }

    /// Asynchronously read a data structure from the socket.
    template <typename T, typename Handler>
    void async_read(T& t, Handler handler)
    {
        // Issue a read operation to read a multicast packet.
        void (mcast_receiver::*f)(
            const boost::system::error_code&,
            std::size_t, T&, boost::tuple<Handler>)
          = &mcast_receiver::handle_mcast_receive<T, Handler>;

        socket_.async_receive_from(
            boost::asio::buffer(packet_, kitty::mcast_max_length),
            sender_endpoint_,
            boost::bind(f, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred,
                boost::ref(t), boost::make_tuple(handler)));
    }

private:
    /// Handle a completed read of a message header. The handler is 
    /// passed using a tuple since boost::bind seems to have trouble 
    /// binding a function object created using boost::bind as a parameter.
    template <typename T, typename Handler>
    void handle_mcast_receive(const boost::system::error_code& e,
        std::size_t bytes_recvd, T& t, boost::tuple<Handler> handler)
    {
      if (e)
      {
        boost::get<0>(handler)(e);
      }
      else
      {
        // Determine the length of the serialized data.
        std::istringstream is(
                std::string(packet_, kitty::mcast_header_length));
        std::size_t data_size = 0;
        if (!(is >> std::hex >> data_size))
        {
          // Header doesn't seem to be valid. Inform the caller.;
          boost::system::error_code error(
                  boost::asio::error::invalid_argument);
          boost::get<0>(handler)(error);
        }
        else
        {
          // Extract the data structure from the data just received.
          try
          {
            std::istringstream archive_stream(
                    std::string(
                        packet_ + kitty::mcast_header_length, data_size));
            boost::archive::text_iarchive archive(archive_stream);
            archive >> t;

            // Inform the caller that the data have been received ok.
            boost::get<0>(handler)(e);
          }
          catch (std::exception& e)
          {
              // Unable to decode data.
              boost::system::error_code error(
                      boost::asio::error::invalid_argument);
              boost::get<0>(handler)(error);
          }
        }
      }

      // Issue another asynchronous read operation.
      async_read(t, boost::get<0>(handler));
    }

private:
    /// The underlying socket
    boost::asio::ip::udp::socket socket_;

    /// The multicast sender endpoint
    boost::asio::ip::udp::endpoint sender_endpoint_;

    /// To hold an inbound packet (header + data)
    char packet_[kitty::mcast_max_length];
};

} // namespace kitty

#endif // KITTY_MCAST_RECEIVER_H

